package com.pijiu.compose.demo00

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.Animatable
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.Spring.DampingRatioHighBouncy
import androidx.compose.animation.core.Spring.DampingRatioLowBouncy
import androidx.compose.animation.core.Spring.DampingRatioMediumBouncy
import androidx.compose.animation.core.Spring.StiffnessHigh
import androidx.compose.animation.core.Spring.StiffnessLow
import androidx.compose.animation.core.Spring.StiffnessMedium
import androidx.compose.animation.core.Spring.StiffnessVeryLow
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateValue
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

/**
 * Jetpack Compose(6)——动画
 *
 * https://www.cnblogs.com/joy99/p/18160974
 */
class Demo04Activity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column {
                AnimateDpAsState()
                Animatable()
                AnimatableColor()
                Transition()
                TransitionClass()
                RemeberInfiniteTransition()
                KeyframesDemo()
            }
        }
    }
}

//animate*AsState 函数是 Compose 动画中最常用的低级别 API 之一，它类似于传统 View 中的属性动画，
// 你只需要提供结束值（或者目标值），API 就会从当前值到目标值开始动画。
@Composable
fun AnimateDpAsState() {
    var bigBox by remember {
        mutableStateOf(false)
    }
    // I'm here
    val boxSize by animateDpAsState(
        targetValue = if (bigBox) 200.dp else 50.dp,
        label = if (bigBox) "bigBox" else "box"
    )
    Box(modifier = Modifier
        .background(Color.Red)
        .size(boxSize) // I'm here
        .clickable {
            bigBox = !bigBox
        })
}

//前面的 animate*AsState 只需要指定目标值，无需指定初始值，而 Animatable 则是一个能够指定初始值的更基础的 API。
// animate*AsState 调用了AnimateValueAsState, 而 AnimateValueAsState 内部使用 Animatable 定制完成。
//
//对于 Animatable 而言，动画数值更新需要在协程中完成，也就是调用 animateTo 方法。
// 此时我们需要确保 Animatable 的初始状态与 LaunchedEffect 代码块首次执行时状态保持一致。
@Composable
fun Animatable() {
    var bigBox by remember {
        mutableStateOf(false)
    }

    //值得注意的是，当我们在 Composable 使用 Animatable 时，其必须包裹在 rememebr 中，
    // 如果你没有这么做，编译器会贴心的提示你添加 rememeber 。
    val customSize = remember {
        Animatable(50.dp, Dp.VectorConverter)
    }

    LaunchedEffect(key1 = bigBox) {
        //同样，与 animate*AsState 一样， animateTo 方法接收 AnimationSpec 参数用来指定动画的规格。
        customSize.animateTo(if (bigBox) 200.dp else 50.dp)
    }

    Box(modifier = Modifier
        .background(Color.Green)
        .size(customSize.value)
        .clickable {
            bigBox = !bigBox
        })
}

@Composable
fun AnimatableColor() {
    var bigBox by remember {
        mutableStateOf(false)
    }

    val customSize = remember {
        Animatable(50.dp, Dp.VectorConverter)
    }

    val customColor = remember {
        Animatable(Color.Blue)
    }

    //另外 Launched 会在 onAtive 时执行，此时要确保， animateTo 的 targetValue 与 Animatable 的默认值相同。
    // 否则在页面首次渲染时，便会发生动画，可能与预期结果不相符。
    LaunchedEffect(key1 = bigBox) {
        //串行
//        customSize.animateTo(if (bigBox) 200.dp else 50.dp)
//        customColor.animateTo(if (bigBox) Color.Red else Color.Blue)

        //并行
        launch{
            customSize.animateTo(if (bigBox) 200.dp else 50.dp)
        }
        launch {
            customColor.animateTo(if (bigBox) Color.Red else Color.Blue)
        }
    }

    Box(modifier = Modifier
        .background(customColor.value)
        .size(customSize.value)
        .clickable {
            bigBox = !bigBox
        })
}

/**
 * Animate*AsState 和 Animatable 都是针对单个目标值的动画，
 * 而 Transition 可以面向多个目标值应用动画，并保持它们同步结束。
 * 这听起来是不是类似传统 View 中的 AnimationSet ？
 */
@Composable
fun Transition() {
    var bigBox by remember {
        mutableStateOf(false)
    }

    val transition = updateTransition(targetState = bigBox, label = "")
    val customSize by transition.animateDp(label = "") {
        when(it) {
            true -> 200.dp
            false -> 50.dp
        }
    }

    val customColor by transition.animateColor(label = "") {
        when(it) {
            true -> Color.Red
            false -> Color.Blue
        }
    }

    Box(modifier = Modifier
        .background(customColor)
        .size(customSize)
        .clickable {
            bigBox = !bigBox
        })
}

//使用 updateTransition 方法操作动画，没有问题，现在假设某个动画效果很复杂，
// 我们不希望每次用的时候都去重新实现一遍，我们希望将上述动画效果封装起来，并可以复用。
// 如何做呢？还是以上面的动画效果为例.
@Preview
@Composable
fun TransitionClass() {
    var bigBox by remember {
        mutableStateOf(false)
    }
    val sizeAndColor = ChangeBoxSizeAndColor(bigBox)
    Box(modifier = Modifier
        .background(sizeAndColor.color)
        .size(sizeAndColor.size)
        .clickable {
            bigBox = !bigBox
        })
}
class TransitionData(
    size: State<Dp>,
    color: State<Color>
) {
    val size by size
    val color by color
}
@Composable
fun ChangeBoxSizeAndColor(bigBox: Boolean): TransitionData {
    val transition = updateTransition(targetState = bigBox, label = "")
    val size = transition.animateDp(label = "") {
        when(it) {
            true -> 200.dp
            false -> 50.dp
        }
    }
    val color = transition.animateColor(label = "") {
        when(it) {
            true -> Color.Red
            false -> Color.Blue
        }
    }
    // 用 remember 包一下
    return remember (transition) {
        TransitionData(size, color)
    }
}

//SpringSpec 弹跳动画
//spring 方法接收三个参数，都有默认值。
//
//dampingRatio： 弹簧的阻尼比，阻尼比可以定义震动从一次弹跳到下一次弹跳所衰减的速度有多快。当阻尼比 < 1 时，阻尼比越小，弹簧越有弹性，默认值为 Spring.DampingRatioNoBouncy = 1f。
//当 dampingRatio > 1 时会出现过阻尼现象，这会使弹簧快速地返回到静止状态。
//当 dampingRatio = 1 时，没有弹性的阻尼比，会使得弹簧在最短的时间内返回到静止状态。
//当 0 < dampingRatio < 1 时, 弹簧会围绕最终静止为止多次反复振动。
//注意 dampingRatio 不能小于0。
//
//const val DampingRatioHighBouncy = 0.2f
//const val DampingRatioMediumBouncy = 0.5f
//const val DampingRatioLowBouncy = 0.75f
//const val DampingRatioNoBouncy = 1f
//
//stiffness： 弹簧的刚度，刚度值越大，弹簧到静止的速度就越快。默认值为Spring.StiffnessMedium = 1500f。
//注意 stiffness 必须大于 0 。
//Compose 为 spring 提供了一组常量值：
//const val StiffnessHigh = 10_000f
//const val StiffnessMedium = 1500f
//const val StiffnessMediumLow = 400f
//const val StiffnessLow = 200f
//const val StiffnessVeryLow = 50f
//
//visibilityThreshold: 可见性阈值。这个参数是一个泛型，次泛型与 targetValue 的类型保持一致，又开发者指定一个阈值，当动画达到这个阈值时，动画会立即停止。默认值为 null。
@Preview
@Composable
fun SpringSpec() {
    var bigBox by remember {
        mutableStateOf(false)
    }
    val customSize by animateDpAsState(
        targetValue = if (bigBox) 200.dp else 50.dp,
        animationSpec = spring(
            dampingRatio = DampingRatioHighBouncy, //弹簧的阻尼比，弹簧收缩的幅度
            stiffness = StiffnessHigh, //弹簧的刚度，弹簧收缩的速度
            visibilityThreshold = null,
        ),
        label = ""
    )
    Box(modifier = Modifier
        .background(Color.LightGray)
        .size(customSize)
        .clickable {
            bigBox = !bigBox
        })
}

//顾名思义，remeberInfiniteTransition 就是一个无限循环的 transition 动画。
// 一旦动画开始便会无限循环下去，直到 Composable 进入 onDispose。
@Preview
@Composable
fun RemeberInfiniteTransition() {
    val infiniteTransition = rememberInfiniteTransition(label = "")
    val color by infiniteTransition.animateColor(
        initialValue = Color.Blue,
        targetValue = Color.Red,
        animationSpec = infiniteRepeatable(
            animation = tween(1000),
            repeatMode = RepeatMode.Reverse
        ),
        label = ""
    )
    val size by infiniteTransition.animateValue(
        initialValue = 50.dp,
        targetValue = 200.dp,
        typeConverter = Dp.VectorConverter,
        animationSpec = infiniteRepeatable(
            animation = tween(1000),
            repeatMode = RepeatMode.Reverse
        ),
        label = ""
    )
    Box(modifier = Modifier
        .background(color)
        .size(size)
    )
}

//KeyframesSpec 也是 DurationBasedAnimationSpec，基于时间的动画规格，
// 在不同的时间戳定义值，更精细地来实现关键帧的动画。可以使用 keyframes() 方法来创建 KeyframesSpec。
@Preview
@Composable
fun KeyframesDemo() {
    var bigBox by remember {
        mutableStateOf(false)
    }
    val customSize by animateDpAsState(
        targetValue = if (bigBox) 200.dp else 50.dp,
        animationSpec = keyframes {
            durationMillis = 10000
            50.dp at 0 with LinearEasing
            100.dp at 1000 with FastOutLinearInEasing
            150.dp at 9000 with LinearEasing
            200.dp at 10000
        },
        label = ""
    )
    Box(modifier = Modifier
        .background(Color.LightGray)
        .size(customSize)
        .clickable {
            bigBox = !bigBox
        })
}

//SnapSpec 表示跳切动画，它立即将动画值捕捉到最终值。它的 targetValue 发生变化时，
// 当前值会立即更新为 targetValue, 没有中间过渡，动画会瞬间完成，常用于跳过过场动画的场景。使用 snap() 方法创建。
@Preview
@Composable
fun SnapDemo() {
    var bigBox by remember {
        mutableStateOf(false)
    }
    val customSize by animateDpAsState(
        targetValue = if (bigBox) 200.dp else 50.dp,
        animationSpec = snap(),
        label = ""
    )
    Box(modifier = Modifier
        .background(Color.LightGray)
        .size(customSize)
        .clickable {
            bigBox = !bigBox
        })
}
